import BoundingSphere from '../Core/BoundingSphere.js';
import Cartesian2 from '../Core/Cartesian2.js';
import Cartesian3 from '../Core/Cartesian3.js';
import Cartesian4 from '../Core/Cartesian4.js';
import ComponentDatatype from '../Core/ComponentDatatype.js';
import defined from '../Core/defined.js';
import defineProperties from '../Core/defineProperties.js';
import destroyObject from '../Core/destroyObject.js';
import IndexDatatype from '../Core/IndexDatatype.js';
import CesiumMath from '../Core/Math.js';
import Matrix4 from '../Core/Matrix4.js';
import PixelFormat from '../Core/PixelFormat.js';
import PrimitiveType from '../Core/PrimitiveType.js';
import Buffer from '../Renderer/Buffer.js';
import BufferUsage from '../Renderer/BufferUsage.js';
import ComputeCommand from '../Renderer/ComputeCommand.js';
import DrawCommand from '../Renderer/DrawCommand.js';
import PixelDatatype from '../Renderer/PixelDatatype.js';
import RenderState from '../Renderer/RenderState.js';
import ShaderProgram from '../Renderer/ShaderProgram.js';
import Texture from '../Renderer/Texture.js';
import VertexArray from '../Renderer/VertexArray.js';
import SunFS from '../Shaders/SunFS.js';
import SunTextureFS from '../Shaders/SunTextureFS.js';
import SunVS from '../Shaders/SunVS.js';
import BlendingState from './BlendingState.js';
import SceneMode from './SceneMode.js';
import SceneTransforms from './SceneTransforms.js';

    /**
     * Draws a sun billboard.
     * <p>This is only supported in 3D and Columbus view.</p>
     *
     * @alias Sun
     * @constructor
     *
     *
     * @example
     * scene.sun = new Cesium.Sun();
     *
     * @see Scene#sun
     */
    function Sun() {
        /**
         * Determines if the sun will be shown.
         *
         * @type {Boolean}
         * @default true
         */
        this.show = true;

        this._drawCommand = new DrawCommand({
            primitiveType : PrimitiveType.TRIANGLES,
            boundingVolume : new BoundingSphere(),
            owner : this
        });
        this._commands = {
            drawCommand : this._drawCommand,
            computeCommand : undefined
        };
        this._boundingVolume = new BoundingSphere();
        this._boundingVolume2D = new BoundingSphere();

        this._texture = undefined;
        this._drawingBufferWidth = undefined;
        this._drawingBufferHeight = undefined;
        this._radiusTS = undefined;
        this._size = undefined;

        this.glowFactor = 1.0;
        this._glowFactorDirty = false;

        this._useHdr = undefined;

        var that = this;
        this._uniformMap = {
            u_texture : function() {
                return that._texture;
            },
            u_size : function() {
                return that._size;
            }
        };
    }

    defineProperties(Sun.prototype, {
        /**
         * Gets or sets a number that controls how "bright" the Sun's lens flare appears
         * to be.  Zero shows just the Sun's disc without any flare.
         * Use larger values for a more pronounced flare around the Sun.
         *
         * @memberof Sun.prototype
         * @type {Number}
         * @default 1.0
         */
        glowFactor : {
            get : function () { return this._glowFactor; },
            set : function (glowFactor) {
                glowFactor = Math.max(glowFactor, 0.0);
                this._glowFactor = glowFactor;
                this._glowFactorDirty = true;
            }
        }
    });

    var scratchPositionWC = new Cartesian2();
    var scratchLimbWC = new Cartesian2();
    var scratchPositionEC = new Cartesian4();
    var scratchCartesian4 = new Cartesian4();

    /**
     * @private
     */
    Sun.prototype.update = function(frameState, passState, useHdr) {
        if (!this.show) {
            return undefined;
        }

        var mode = frameState.mode;
        if (mode === SceneMode.SCENE2D || mode === SceneMode.MORPHING) {
            return undefined;
        }

        if (!frameState.passes.render) {
            return undefined;
        }

        var context = frameState.context;
        var drawingBufferWidth = passState.viewport.width;
        var drawingBufferHeight = passState.viewport.height;

        if (!defined(this._texture) ||
                drawingBufferWidth !== this._drawingBufferWidth ||
                drawingBufferHeight !== this._drawingBufferHeight ||
                this._glowFactorDirty ||
                useHdr !== this._useHdr) {
            this._texture = this._texture && this._texture.destroy();
            this._drawingBufferWidth = drawingBufferWidth;
            this._drawingBufferHeight = drawingBufferHeight;
            this._glowFactorDirty = false;
            this._useHdr = useHdr;

            var size = Math.max(drawingBufferWidth, drawingBufferHeight);
            size = Math.pow(2.0, Math.ceil(Math.log(size) / Math.log(2.0)) - 2.0);

            // The size computed above can be less than 1.0 if size < 4.0. This will probably
            // never happen in practice, but does in the tests. Clamp to 1.0 to prevent WebGL
            // errors in the tests.
            size = Math.max(1.0, size);

            var pixelDatatype = useHdr ? (context.halfFloatingPointTexture ? PixelDatatype.HALF_FLOAT : PixelDatatype.FLOAT) : PixelDatatype.UNSIGNED_BYTE;
            this._texture = new Texture({
                context : context,
                width : size,
                height : size,
                pixelFormat : PixelFormat.RGBA,
                pixelDatatype : pixelDatatype
            });

            this._glowLengthTS = this._glowFactor * 5.0;
            this._radiusTS = (1.0 / (1.0 + 2.0 * this._glowLengthTS)) * 0.5;

            var that = this;
            var uniformMap = {
                u_radiusTS : function() {
                    return that._radiusTS;
                }
            };

            this._commands.computeCommand = new ComputeCommand({
                fragmentShaderSource : SunTextureFS,
                outputTexture  : this._texture,
                uniformMap : uniformMap,
                persists : false,
                owner : this,
                postExecute : function() {
                    that._commands.computeCommand = undefined;
                }
            });
        }

        var drawCommand = this._drawCommand;

        if (!defined(drawCommand.vertexArray)) {
            var attributeLocations = {
                direction : 0
            };

            var directions = new Uint8Array(4 * 2);
            directions[0] = 0;
            directions[1] = 0;

            directions[2] = 255;
            directions[3] = 0.0;

            directions[4] = 255;
            directions[5] = 255;

            directions[6] = 0.0;
            directions[7] = 255;

            var vertexBuffer = Buffer.createVertexBuffer({
                context : context,
                typedArray : directions,
                usage : BufferUsage.STATIC_DRAW
            });
            var attributes = [{
                index : attributeLocations.direction,
                vertexBuffer : vertexBuffer,
                componentsPerAttribute : 2,
                normalize : true,
                componentDatatype : ComponentDatatype.UNSIGNED_BYTE
            }];
            // Workaround Internet Explorer 11.0.8 lack of TRIANGLE_FAN
            var indexBuffer = Buffer.createIndexBuffer({
                context : context,
                typedArray : new Uint16Array([0, 1, 2, 0, 2, 3]),
                usage : BufferUsage.STATIC_DRAW,
                indexDatatype : IndexDatatype.UNSIGNED_SHORT
            });
            drawCommand.vertexArray = new VertexArray({
                context : context,
                attributes : attributes,
                indexBuffer : indexBuffer
            });

            drawCommand.shaderProgram = ShaderProgram.fromCache({
                context : context,
                vertexShaderSource : SunVS,
                fragmentShaderSource : SunFS,
                attributeLocations : attributeLocations
            });

            drawCommand.renderState = RenderState.fromCache({
                blending : BlendingState.ALPHA_BLEND
            });
            drawCommand.uniformMap = this._uniformMap;
        }

        var sunPosition = context.uniformState.sunPositionWC;
        var sunPositionCV = context.uniformState.sunPositionColumbusView;

        var boundingVolume = this._boundingVolume;
        var boundingVolume2D = this._boundingVolume2D;

        Cartesian3.clone(sunPosition, boundingVolume.center);
        boundingVolume2D.center.x = sunPositionCV.z;
        boundingVolume2D.center.y = sunPositionCV.x;
        boundingVolume2D.center.z = sunPositionCV.y;

        boundingVolume.radius = CesiumMath.SOLAR_RADIUS + CesiumMath.SOLAR_RADIUS * this._glowLengthTS;
        boundingVolume2D.radius = boundingVolume.radius;

        if (mode === SceneMode.SCENE3D) {
            BoundingSphere.clone(boundingVolume, drawCommand.boundingVolume);
        } else if (mode === SceneMode.COLUMBUS_VIEW) {
            BoundingSphere.clone(boundingVolume2D, drawCommand.boundingVolume);
        }

        var position = SceneTransforms.computeActualWgs84Position(frameState, sunPosition, scratchCartesian4);

        var dist = Cartesian3.magnitude(Cartesian3.subtract(position, frameState.camera.position, scratchCartesian4));
        var projMatrix = context.uniformState.projection;

        var positionEC = scratchPositionEC;
        positionEC.x = 0;
        positionEC.y = 0;
        positionEC.z = -dist;
        positionEC.w = 1;

        var positionCC = Matrix4.multiplyByVector(projMatrix, positionEC, scratchCartesian4);
        var positionWC = SceneTransforms.clipToGLWindowCoordinates(passState.viewport, positionCC, scratchPositionWC);

        positionEC.x = CesiumMath.SOLAR_RADIUS;
        var limbCC = Matrix4.multiplyByVector(projMatrix, positionEC, scratchCartesian4);
        var limbWC = SceneTransforms.clipToGLWindowCoordinates(passState.viewport, limbCC, scratchLimbWC);

        this._size = Cartesian2.magnitude(Cartesian2.subtract(limbWC, positionWC, scratchCartesian4));
        this._size = 2.0 * this._size * (1.0 + 2.0 * this._glowLengthTS);
        this._size = Math.ceil(this._size);

        return this._commands;
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     *
     * @see Sun#destroy
     */
    Sun.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * sun = sun && sun.destroy();
     *
     *  @see Sun#isDestroyed
     */
    Sun.prototype.destroy = function() {
        var command = this._drawCommand;
        command.vertexArray = command.vertexArray && command.vertexArray.destroy();
        command.shaderProgram = command.shaderProgram && command.shaderProgram.destroy();

        this._texture = this._texture && this._texture.destroy();

        return destroyObject(this);
    };
export default Sun;
